﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Linq.Expressions;
using Microsoft.MediaPlayer.Interop;
using System.IO;
using System.Diagnostics;
using System.Reflection;

namespace BMproductions.Media.MediaPlayer.WMPPlus {
	public static class WMPATF {
		enum MediaTags {
			/*0*/AcquisitionTime,
			/*1*/AlbumID,
			/*2*/AlbumIDAlbumArtist,
			/*3*/Author,
			/*4*/AverageLevel,
			/*5*/Bitrate,
			/*6*/BuyNow,
			/*7*/BuyTickets,
			/*8*/Channels,
			/*9*/Copyright,
			/*10*/CurrentBitrate,
			/*11*/Description,
			/*12*/Duration,
			/*13*/FileSize,
			/*14*/FileType,
			/*15*/Is_Protected,
			/*16*/IsVBR,
			/*17*/MediaType,
			/*18*/MoreInfo,
			/*19*/PeakValue,
			/*20*/PlaylistIndex,
			/*21*/ProviderLogoURL,
			/*22*/ProviderURL,
			/*23*/RecordingTime,
			/*24*/RecordingTimeDay,
			/*25*/RecordingTimeMonth,
			/*26*/RecordingTimeYear,
			/*27*/RecordingTimeYearMonth,
			/*28*/RecordingTimeYearMonthDay,
			/*29*/ReleaseDate,
			/*30*/ReleaseDateDay,
			/*31*/ReleaseDateMonth,
			/*32*/ReleaseDateYear,
			/*33*/ReleaseDateYearMonth,
			/*34*/ReleaseDateYearMonthDay,
			/*35*/RequestState,
			/*36*/ShadowFilePath,
			/*37*/SourceURL,
			/*38*/SyncState,
			/*39*/Title,
			/*40*/TrackingID,
			/*41*/UserCustom1,
			/*42*/UserCustom2,
			/*43*/UserEffectiveRating,
			/*44*/UserLastPlayedTime,
			/*45*/UserPlayCount,
			/*46*/UserPlaycountAfternoon,
			/*47*/UserPlaycountEvening,
			/*48*/UserPlaycountMorning,
			/*49*/UserPlaycountNight,
			/*50*/UserPlaycountWeekday,
			/*51*/UserPlaycountWeekend,
			/*52*/UserRating,
			/*53*/UserServiceRating,
			/*54*/WM_AlbumArtist,
			/*55*/WM_AlbumTitle,
			/*56*/WM_BeatsPerMinute,
			/*57*/WM_Category,
			/*58*/WM_Composer,
			/*59*/WM_Conductor,
			/*60*/WM_ContentDistributor,
			/*61*/WM_ContentGroupDescription,
			/*62*/WM_EncodingTime,
			/*63*/WM_Genre,
			/*64*/WM_GenreID,
			/*65*/WM_InitialKey,
			/*66*/WM_ISRC,
			/*67*/WM_Language,
			/*68*/WM_Lyrics,
			/*69*/WM_MCDI,
			/*70*/WM_MediaClassPrimaryID,
			/*71*/WM_MediaClassSecondaryID,
			/*72*/WM_Mood,
			/*73*/WM_OriginalAlbumTitle,
			/*74*/WM_ParentalRating,
			/*75*/WM_PartOfSet,
			/*76*/WM_Period,
			/*77*/WM_ProtectionType,
			/*78*/WM_Provider,
			/*79*/WM_ProviderRating,
			/*80*/WM_ProviderStyle,
			/*81*/WM_Publisher,
			/*82*/WM_SubscriptionContentID,
			/*83*/WM_SubTitle,
			/*84*/WM_TrackNumber,
			/*85*/WM_UniqueFileIdentifier,
			/*86*/WM_WMCollectionGroupID,
			/*87*/WM_WMCollectionID,
			/*88*/WM_WMContentID,
			/*89*/WM_Writer,
			/*90*/WM_Year
		}

		private static IDictionary<string, TagzFMT> cache = new Dictionary<string, TagzFMT>();
		private static readonly IDictionary<string, ATFMapping> mapping = new Dictionary<string, ATFMapping>();
		private static readonly string[] mediaTags = Enum.GetNames(typeof(MediaTags));

		private class ATFMapping {
			//string name;
			public MediaTags Code;
			public ExpressionMethod Post;
			public ExpressionMethod Parse;
			public ATFMapping(MediaTags code, ExpressionMethod post, ExpressionMethod parse) {
				this.Code = code;
				this.Post = post;
				this.Parse = parse;
			}
			public ATFMapping(MediaTags code) {
				this.Code = code;
				this.Post = ExpressionMethod.None;
				this.Parse = ExpressionMethod.None;
			}
		}

		static int? NumberTag(IWMPMedia3 media, string type) {
			try {
				int count = media.getAttributeCountByType(type, null);
				object item = count > 0 ? media.getItemInfoByType(type, null, 0) : null;
				if (item == null) return null;
				return Convert.ToInt32(item);
			} catch (Exception ex) {
				Trace.WriteLine(String.Format("NumberTag({0}): {1}", type, ex.Message));
				Debug.WriteLine(ex.ToString());
				return null;
			}
		}

		static string RatingTag(int? rating) {
			if (rating == null)
				return null;
			return WMPUtils.Rating2Starts((int)rating).ToString();
		}

		static string LengthTag(int? len) {
			if (len == null)
				return null;
			return ((int)len * 1000).ToString();
		}

		static string LengthFTag(int? len) {
			if (len == null)
				return null;
			int min = (int)len / 60;
			int sec = (int)len % 60;
			return String.Format("{0:2}.{1:2}", min, sec);
		}

		static string BitrateTag(int? bitrate) {
			if (bitrate == null)
				return null;
			return ((int)bitrate / 1000).ToString();
		}

		static string MediaTag(IWMPMedia media, string item) {
			try {
				return media.getItemInfo(item);
			} catch {
				return null;
			}
		}

		static string VBRTag(string value) {
			if (String.IsNullOrEmpty(value))
				return "0";
			return Map(value, new string[] { "false", "true" }, new string[] { "0", "1" });
		}

		static string TrackArtistTag(IWMPMedia3 media, string artist) {
			if (artist != null)
				try {
					if (String.Compare(media.getItemInfo(mediaTags[(int)MediaTags.WM_AlbumArtist]), artist, true) == 0)
						return String.Empty;
				} catch { }
			return artist;
		}

		static string TypeTag(string value) {
			return Map(value, new string[] { "audio", "video" }, new string[] { "0", "1" });
		}

		enum ExpressionMethod {
			Rating,
			Length,
			LengthF,
			Bitrate,
			Media,
			VBR,
			TrackArtist,
			Type,
			GetDirectoryName,
			None
		}

		static Expression<Func<int?>> numberTemplate = () => NumberTag(null, null);
		static Expression<Func<string>>[] templates =
		{
			() => RatingTag(0),
			() => LengthTag(0),
			() => LengthFTag(0),
			() => BitrateTag(0),
			() => MediaTag(null, null),
			() => VBRTag(null),
			() => TrackArtistTag(null, null),
			() => TypeTag(null),
			() => Path.GetDirectoryName(null),
		};

		static readonly string[] tags2 = new string[] {
			"artist", "title", "album", "genre", "composer", "albumartist", "conductor"
		};

		static WMPATF() {
			mapping.Add("filename", new ATFMapping(MediaTags.SourceURL));
			mapping.Add("folder", new ATFMapping(MediaTags.SourceURL, ExpressionMethod.GetDirectoryName, ExpressionMethod.None));
			mapping.Add("playcount", new ATFMapping(MediaTags.UserPlayCount));
			mapping.Add("year", new ATFMapping(MediaTags.ReleaseDateYear));
			mapping.Add("comment", new ATFMapping(MediaTags.Description));
			mapping.Add("tracknumber", new ATFMapping(MediaTags.WM_TrackNumber));
			mapping.Add("track", new ATFMapping(MediaTags.WM_TrackNumber));
			mapping.Add("disc", new ATFMapping(MediaTags.WM_PartOfSet));
			mapping.Add("publisher", new ATFMapping(MediaTags.WM_Publisher));
			mapping.Add("type", new ATFMapping(MediaTags.MediaType, ExpressionMethod.Type, ExpressionMethod.None));
			mapping.Add("vbr", new ATFMapping(MediaTags.IsVBR, ExpressionMethod.VBR, ExpressionMethod.None));
			mapping.Add("category", new ATFMapping(MediaTags.WM_ContentGroupDescription));
			mapping.Add("trackartist", new ATFMapping(MediaTags.Author, ExpressionMethod.TrackArtist, ExpressionMethod.None));
			mapping.Add("bpm", new ATFMapping(MediaTags.WM_BeatsPerMinute));
			mapping.Add("key", new ATFMapping(MediaTags.WM_InitialKey));
			mapping.Add("lyricist", new ATFMapping(MediaTags.WM_Writer));
			mapping.Add("isrc", new ATFMapping(MediaTags.WM_ISRC));
			mapping.Add("rating", new ATFMapping(MediaTags.UserRating, ExpressionMethod.None, ExpressionMethod.Rating));
			mapping.Add("length", new ATFMapping(MediaTags.Duration, ExpressionMethod.None, ExpressionMethod.Length));
			mapping.Add("lengthf", new ATFMapping(MediaTags.Duration, ExpressionMethod.None, ExpressionMethod.LengthF));
			mapping.Add("bitrate", new ATFMapping(MediaTags.Bitrate, ExpressionMethod.None, ExpressionMethod.Bitrate));

			for (int k = 0; k < mediaTags.Length; k++)
				mediaTags[k] = mediaTags[k].Replace('_', '/');
		}

		static string Map(string value, string[] from, string[] to) {
			if (String.IsNullOrEmpty(value))
				return null;
			for (int k = 0; k < from.Length; k++)
				if (String.Compare(value, from[k], true) == 0)
					return to[k];
			return value;
		}

		static MethodInfo template(ExpressionMethod method) {
			return ((MethodCallExpression)templates[(int)method].Body).Method;
		}

		static Expression BuildTag(string tag, ParameterExpression media, ParameterExpression data) {
			ATFMapping map;
			string item = tag;
			Expression res;
			if (mapping.TryGetValue(tag, out map)) {
				item = mediaTags[(int)map.Code];
			}
			if (map != null && map.Parse != ExpressionMethod.None) {
				res = Expression.Call(null,
					((MethodCallExpression)numberTemplate.Body).Method, media,
					Expression.Constant(item));
				res = Expression.Call(null, template(map.Parse), res);
			} else {
				res = Expression.Call(null,
					template(ExpressionMethod.Media), media,
					Expression.Constant(item));
			}
			if (map != null && map.Post != ExpressionMethod.None) {
				var post = template(map.Post);
				if (post.GetParameters().Length == 2)
					res = Expression.Call(null, post, media, res);
				else
					res = Expression.Call(null, post, res);
			}
			return res;
		}

		public static string BuildTitle(IWMPMedia3 media, string format, TagzFMT.BuildTagDelegate func, string usage, object data) {
			TagzFMT tagz;
			if (!cache.TryGetValue(usage, out tagz))
				lock (cache) {
					if (!cache.TryGetValue(usage, out tagz)) {
						tagz = new TagzFMT(format, func ?? BuildTag);
						cache.Add(usage, tagz);
					}
				}
			tagz.Format = format;
			return tagz.Build(media, data);
		}

		public static string[] AvailableTags {
			get {
				var tags = new List<string>();
				tags.AddRange(mapping.Keys);
				tags.AddRange(tags2);
				return tags.ToArray();
			}
		}
	}
}
